package ru.susu.algebra.wrapper.number;

import java.math.BigInteger;

/**
 * @author akargapolov
 * @since: 22.02.2010
 */
public class BigIntegerFraction implements INumberWrapper<BigIntegerFraction>, Comparable<BigIntegerFraction>
{
	public static final BigIntegerFraction ONE = new BigIntegerFraction(1, 1);
	public static final BigIntegerFraction TEN = new BigIntegerFraction(10, 1);
	public static final BigIntegerFraction MINUS_ONE = new BigIntegerFraction(-1, 1);
	public static final BigIntegerFraction ZERO = new BigIntegerFraction(0, 1);

	private BigInteger _numerator;
	private BigInteger _denominator;

	public BigIntegerFraction(long numerator)
	{
		this(BigInteger.valueOf(numerator), BigInteger.ONE);
	}

	public BigIntegerFraction(BigInteger numerator)
	{
		this(numerator, BigInteger.ONE);
	}

	public BigIntegerFraction(long numerator, long denominator)
	{
		this(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator));
	}

	public BigIntegerFraction(BigInteger numerator, BigInteger denominator)
	{
		_numerator = numerator;
		_denominator = denominator;
		removeCommonDivisor();
	}

	public BigInteger getNumerator()
	{
		return _numerator;
	}

	public BigInteger getDenominator()
	{
		return _denominator;
	}

	@Override
	public BigIntegerFraction add(INumberWrapper<BigIntegerFraction> other)
	{
		BigIntegerFraction fraction = (BigIntegerFraction)other;
		BigInteger numerator = _numerator.multiply(fraction._denominator).add(fraction._numerator.multiply(_denominator));
		BigInteger denominator = _denominator.multiply(fraction._denominator);
		BigIntegerFraction result = new BigIntegerFraction(numerator, denominator);
		result.removeCommonDivisor();
		return result;
	}

	@Override
	public BigIntegerFraction subtract(INumberWrapper<BigIntegerFraction> other)
	{
		BigIntegerFraction fraction = (BigIntegerFraction)other;
		BigInteger numerator = _numerator.multiply(fraction._denominator).subtract(fraction._numerator.multiply(_denominator));
		BigInteger denominator = _denominator.multiply(fraction._denominator);
		BigIntegerFraction result = new BigIntegerFraction(numerator, denominator);
		result.removeCommonDivisor();
		return result;
	}

	@Override
	public BigIntegerFraction divide(INumberWrapper<BigIntegerFraction> other)
	{
		BigIntegerFraction fraction = (BigIntegerFraction)other;
		BigIntegerFraction result = new BigIntegerFraction(_numerator.multiply(fraction._denominator), _denominator.multiply(fraction._numerator));
		result.removeCommonDivisor();
		return result;
	}

	@Override
	public BigIntegerFraction multiply(INumberWrapper<BigIntegerFraction> other)
	{
		BigIntegerFraction fraction = (BigIntegerFraction)other;
		BigIntegerFraction result = new BigIntegerFraction(_numerator.multiply(fraction._numerator), _denominator.multiply(fraction._denominator));
		result.removeCommonDivisor();
		return result;
	}

	private void removeCommonDivisor()
	{
		if (_numerator.equals(BigInteger.ZERO))
		{
			_denominator = BigInteger.ONE;
			return;
		}

		BigInteger gcd = _numerator.gcd(_denominator);
		if (!gcd.equals(BigInteger.ONE))
		{
			_numerator = _numerator.divide(gcd);
			_denominator = _denominator.divide(gcd);
		}

		if (_denominator.compareTo(BigInteger.ZERO) < 0)
		{
			_numerator = _numerator.multiply(BigInteger.valueOf(-1));
			_denominator = _denominator.multiply(BigInteger.valueOf(-1));
		}
	}

	@Override
	public BigIntegerFraction getValue()
	{
		return this;
	}

	@Override
	public long remainder(long val)
	{
		throw new RuntimeException("unsupported operation");
	}

	@Override
	public void setValue(Number value)
	{
		throw new RuntimeException("unsupported operation");
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + ((_denominator == null) ? 0 : _denominator.hashCode());
		result = prime * result + ((_numerator == null) ? 0 : _numerator.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		BigIntegerFraction other = (BigIntegerFraction) obj;
		if (_denominator == null)
		{
			if (other._denominator != null)
				return false;
		}
		else if (!_denominator.equals(other._denominator))
			return false;
		if (_numerator == null)
		{
			if (other._numerator != null)
				return false;
		}
		else if (!_numerator.equals(other._numerator))
			return false;
		return true;
	}

	@Override
	public int compareTo(BigIntegerFraction other)
	{
		BigInteger mynumerator = _numerator.multiply(other._denominator);
		BigInteger othernumerator = other._numerator.multiply(_denominator);
		return mynumerator.compareTo(othernumerator);
	}

	@Override
	public String toString()
	{
		if (_denominator.equals(BigInteger.ONE))
		{
			return _numerator + "";
		}
		return _numerator + " / " + _denominator;
	}

	@Override
	public Object clone()
	{
		return new BigIntegerFraction(_numerator, _denominator);
	}

	@Override
	public INumberWrapper<BigIntegerFraction> mod(BigInteger mod) throws Exception
	{
		throw new RuntimeException();
	}
}
